package https

import (
	"bytes"
	"compress/flate"
	"compress/gzip"
	"context"
	"crypto/md5"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/andybalholm/brotli"
)

// 网络请求操作

// 使用GET请求
// 此处仅能使用 `Param` 进行设置请求参数
func (c *CURL) Get() *CURL {
	c.hookBefore() // 请求前操作
	if c.Error != nil || len(c.files) > 0 || c.option.cacheHit {
		return c.handleCommonErrors()
	}
	// 进行GET请求
	req, err := http.NewRequest("GET", c.getParamUrl("-"), nil)
	if err != nil {
		c.Error = err
	}
	c.option.method = "get"
	return c.queryBody(req)
}

// 使用POST请求
// 此处仅能使用 `Param` 进行设置请求参数
func (c *CURL) Post() *CURL {
	c.
		headerDefault("Content-Type", "application/x-www-form-urlencoded").
		hookBefore()
	if c.Error != nil || c.option.cacheHit {
		return c.handleCommonErrors()
	}
	var req *http.Request
	var err error
	if len(c.files) > 0 {
		// 传输文件的话
		bodyBuffer := &bytes.Buffer{}
		bodyWriter := multipart.NewWriter(bodyBuffer)
		// 读取文件
		for k, v := range c.files {
			if err := c.handleFileError(k, v, bodyWriter); err != nil {
				return c
			}
		}
		for k, v := range c.ParamQuest {
			if err := bodyWriter.WriteField(k, v); err != nil {
				c.Error = fmt.Errorf("参数 %s 追加失败：%w", v, err)
				return c
			}
		}
		c.HeaderKV("Content-Type", bodyWriter.FormDataContentType())
		bodyWriter.Close()
		req, err = http.NewRequest("POST", c.URI, bodyBuffer)
	} else {
		req, err = http.NewRequest("POST", c.URI, strings.NewReader(c.getParamUrl(".")))
	}
	if err != nil {
		c.Error = err
	}
	c.option.method = "post"
	return c.queryBody(req)
}

// 使用POST请求
// 此处使用 `ParamJson` 进行设置请求参数，支持使用 `Param` 设置的参数列表
func (c *CURL) PostJson() *CURL {
	c.hookBefore()
	if c.Error != nil || len(c.files) > 0 || c.option.cacheHit {
		return c.handleCommonErrors()
	}
	var j []byte
	if len(c.ParamJsonQuest) > 0 {
		if v, ok := c.ParamJsonQuest["_"]; ok && len(c.ParamJsonQuest) == 1 {
			j, _ = json.Marshal(v)
		} else {
			j, _ = json.Marshal(c.ParamJsonQuest)
		}
	} else {
		j, _ = json.Marshal(c.ParamQuest)
	}
	req, err := http.NewRequest("POST", c.URI, strings.NewReader(string(j)))
	if err != nil {
		c.Error = err
	}
	c.option.method = "postjson"
	return c.queryBody(req)
}

// 下载文件，将请求的内容保存到本地
//
//	file	保存文件名
func (c *CURL) Download(file string) *CURL {
	if c.Error != nil || c.HttpCode == 0 && len(c.Body) == 0 {
		return c.handleDownloadErrors(file)
	}
	// 写入文件
	return c.writeToFile(file)
}

// 下载文件到对外IO中进行重定向
//
//	f	写入对象的IO方法
func (c *CURL) DownloadIO(f io.Writer) *CURL {
	if c.Error != nil || c.HttpCode == 0 && len(c.Body) == 0 {
		return c.handleDownloadErrors("")
	}
	f.Write([]byte(c.Body))
	return c
}

// 获取请求客户端标识
func (c *CURL) getClient() *http.Client {
	c.StartTime = time.Now()
	// 如果传入了Client选项，则直接使用传入的client项
	cl := &http.Client{}
	if c.option.jar != nil {
		cl.Jar = c.option.jar
	}
	tra := http.Transport{}
	// 跳过HTTPS证书
	if c.option.httpsContinue {
		tra.TLSClientConfig = &tls.Config{
			Renegotiation:      tls.RenegotiateFreelyAsClient,
			InsecureSkipVerify: true,
		}
	}
	// 禁用Http2
	if c.option.disableHttpV2 {
		tra.ForceAttemptHTTP2 = false
		tra.TLSNextProto = make(map[string]func(authority string, c *tls.Conn) http.RoundTripper)
	}
	// 进行本地host域名解析
	if len(c.option.hosts) > 0 {
		tra.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
			ls := strings.Split(addr, ":")
			if addrs, ok := c.option.hosts[ls[0]]; ok {
				addr = addrs + ":" + ls[1]
			} else if addrs, ok := c.option.hosts[addr]; ok {
				addr = addrs
			}
			dialer := net.Dialer{}
			return dialer.DialContext(ctx, network, addr)
		}
	}
	cl.Transport = &tra
	// 设置超时时间
	if c.option.timeOut > 0 {
		cl.Timeout = c.option.timeOut
	}
	return cl
}

// 获取缓存下标
func (c *CURL) getKey() string {
	return fmt.Sprintf("https_cache_%0x", md5.Sum([]byte(fmt.Sprintf("%T: %v, param: %T: %v, param_json: %T: %v, header: %T: %v",
		c.URI, c.URI,
		c.ParamQuest, c.ParamQuest,
		c.ParamJsonQuest, c.ParamJsonQuest,
		c.HeaderQuest, c.HeaderQuest,
	))))
}

// 获取CURL的GET请求网址
//
//	uri	要拼接的URI前缀，传入-表示使用CURL中的URI网址 ，传入.表示只返回拼接的URL参数
func (c *CURL) getParamUrl(uri string) string {
	if c.Error != nil {
		return ""
	}
	dat := url.Values{}
	for i, v := range c.ParamQuest {
		dat.Set(i, v)
	}
	if c.URI == "" {
		c.Error = errors.New("网址不能为空")
		return dat.Encode()
	}
	switch uri {
	case "-":
		uri = c.URI
	case ".":
		return dat.Encode()
	}
	if strings.Contains(uri, "?") {
		uri += "&" + dat.Encode()
	} else {
		uri += "?" + dat.Encode()
	}
	return uri
}

// 从quest结构体中获取到数据并回写到CURL结构体中
//
//	req		请求的待处理操作
func (curl *CURL) queryBody(req *http.Request) *CURL {
	if curl.Error != nil {
		return curl.retry()
	}
	// 请求开始前的钩子处理
	if hook, ok := _default.hookBefore["*"]; ok {
		hook(curl)
	}
	if uri, err := url.Parse(curl.URI); err == nil {
		if hook, ok := _default.hookBefore[uri.Host]; ok {
			hook(curl)
		}
	}
	req = req.WithContext(curl.option.context)
	defer curl.hookEnd()
	// 设置Header请求头
	for i, v := range curl.HeaderQuest {
		req.Header.Set(i, v)
	}
	// 执行HTTP请求
	resp, err := curl.getClient().Do(req)
	if err != nil {
		curl.Error = err
		return curl.retry()
	}
	defer resp.Body.Close()
	// 获取相应结果
	bodys, err := io.ReadAll(resp.Body)
	if err != nil {
		curl.Error = err
		return curl.retry()
	}
	// 判断返回的编码是否经过了压缩
	switch resp.Header.Get("Content-Encoding") {
	case "gzip":
		ioReader := bytes.NewBuffer(bodys)
		gzReader, err := gzip.NewReader(ioReader)
		if err == nil {
			defer gzReader.Close()
			buf := bytes.Buffer{}
			if _, err := io.Copy(&buf, gzReader); err == nil {
				bodys = buf.Bytes()
			}
		}
	case "deflate":
		ioReader := bytes.NewBuffer(bodys)
		deflateReader := flate.NewReader(ioReader)
		buf := bytes.Buffer{}
		if _, err := io.Copy(&buf, deflateReader); err == nil {
			bodys = buf.Bytes()
		}
	case "br":
		ioReader := bytes.NewBuffer(bodys)
		// Go原生未实现br压缩方式，此处引入外部依赖进行支持
		brReader := brotli.NewReader(ioReader)
		var buf bytes.Buffer
		if _, err := io.Copy(&buf, brReader); err == nil {
			bodys = buf.Bytes()
		}
	}
	curl.Body = string(bodys)
	curl.HttpCode = resp.StatusCode
	curl.CookieQuest = resp.Header.Values("set-cookie")
	curl.Version = 1.1
	if f, err := strconv.ParseFloat(fmt.Sprintf("%d.%d", resp.ProtoMajor, resp.ProtoMinor), 64); err == nil {
		curl.Version = f
	}
	// HTTP-Code错误
	if curl.HttpCode > 300 || curl.HttpCode < 200 {
		curl.Error = fmt.Errorf("网络请求Code返回错误：%d", curl.HttpCode)
	}
	if hook, ok := _default.hookAfter["*"]; ok {
		hook(curl)
	}
	// 请求结束后的钩子处理
	if uri, err := url.Parse(curl.URI); err == nil {
		if hook, ok := _default.hookAfter[uri.Host]; ok {
			hook(curl)
		}
	}
	return curl.retry()
}

// 获取域名下的cookie信息
// 如果使用了jar进行cookie管理，会直接返回jar中该域名下的cookie
// 如果未使用jar进行管理，则会获取CookieQuest的值，然后使用;=进行分割，以提取其中的key/value值
// 响应的，如果未使用jar进行管理，此处会手动循环cookie中的值进行strings切分，所以此处速度会稍慢一些
func (c *CURL) Cookie() []*http.Cookie {
	u, err := url.Parse(c.URI)
	if err != nil {
		return nil
	}
	if c.option == nil || c.option.jar == nil {
		if len(c.CookieQuest) == 0 {
			return nil
		}
		cookie, err := cookiejar.New(nil)
		if err != nil {
			return nil
		}
		ck := []*http.Cookie{}
		for i := 0; i < len(c.CookieQuest); i++ {
			// 使用;切分数组
			temps := strings.Split(c.CookieQuest[i], ";")
			// 使用= 切分数组
			tmp := strings.Split(temps[0], "=")
			ck_one := http.Cookie{
				Name:  tmp[0],
				Value: strings.Join(tmp[1:], "="),
			}
			for i := 0; i < len(temps); i++ {
				if i == 0 {
					continue
				}
				tmp := strings.Split(temps[i], "=")
				switch tmp[0] {
				case "expires":
					t, err := time.Parse(time.RFC850, tmp[1])
					if err != nil {
						continue
					}
					ck_one.Expires = t
				case "max-age":
					if s, err := strconv.Atoi(tmp[1]); err == nil {
						ck_one.MaxAge = int(s)
					}
				case "path":
					ck_one.Path = tmp[1]
				case "domain":
					ck_one.Domain = tmp[1]
				}
			}
			ck = append(ck, &ck_one)
		}
		cookie.SetCookies(u, ck)
		return cookie.Cookies(u)
	}
	return c.option.jar.Cookies(u)
}

// 处理常见错误
func (c *CURL) handleCommonErrors() *CURL {
	if c.Error != nil {
		return c
	}
	if len(c.files) > 0 {
		c.Error = fmt.Errorf("文件上传暂只支持PostForm形式进行上传")
	} else if c.option.cacheHit {
		c.EndTime = time.Now()
	}
	return c
}

// 处理文件错误
func (c *CURL) handleFileError(k, v string, bodyWriter *multipart.Writer) error {
	_, err := os.Stat(v)
	if err != nil {
		c.Error = fmt.Errorf("文件 %s 未找到：%w", v, err)
		return err
	}
	fs, err := os.Open(v)
	if err != nil {
		c.Error = fmt.Errorf("文件 %s 打开失败：%w", v, err)
		return err
	}
	defer fs.Close()
	fileWriter1, _ := bodyWriter.CreateFormFile(k, v)
	io.Copy(fileWriter1, fs)
	return nil
}

// 处理下载错误
func (c *CURL) handleDownloadErrors(file string) *CURL {
	if c.Error != nil {
		return c
	}
	if c.HttpCode == 0 && len(c.Body) == 0 {
		// 该请求还未进行最终的GET/POST请求，所以此处直接默认为GET请求
		c.Get()
	}
	return c
}

// 写入文件
func (c *CURL) writeToFile(file string) *CURL {
	f, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		c.Error = err
		return c
	}
	defer f.Close()
	f.WriteString(c.Body)
	return c
}
